// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_MICROTASK_QUEUE_H_
#define V8_MICROTASK_QUEUE_H_

#include <stdint.h>
#include <memory>
#include <vector>

#include "include/v8-internal.h" // For Address.
#include "include/v8.h"
#include "src/base/macros.h"

namespace v8 {
namespace internal {

    class Isolate;
    class Microtask;
    class Object;
    class RootVisitor;

    class V8_EXPORT_PRIVATE MicrotaskQueue final : public v8::MicrotaskQueue {
    public:
        static void SetUpDefaultMicrotaskQueue(Isolate* isolate);
        static std::unique_ptr<MicrotaskQueue> New(Isolate* isolate);

        ~MicrotaskQueue();

        // Uses raw Address values because it's called via ExternalReference.
        // {raw_microtask} is a tagged Microtask pointer.
        // Returns a tagged Object pointer.
        static Address CallEnqueueMicrotask(Isolate* isolate,
            intptr_t microtask_queue_pointer,
            Address raw_microtask);

        // v8::MicrotaskQueue implementations.
        void EnqueueMicrotask(v8::Isolate* isolate,
            v8::Local<Function> microtask) override;
        void EnqueueMicrotask(v8::Isolate* isolate, v8::MicrotaskCallback callback,
            void* data) override;
        void PerformCheckpoint(v8::Isolate* isolate) override;

        void EnqueueMicrotask(Microtask microtask);
        void AddMicrotasksCompletedCallback(
            MicrotasksCompletedCallbackWithData callback, void* data) override;
        void RemoveMicrotasksCompletedCallback(
            MicrotasksCompletedCallbackWithData callback, void* data) override;
        bool IsRunningMicrotasks() const override { return is_running_microtasks_; }

        // Runs all queued Microtasks.
        // Returns -1 if the execution is terminating, otherwise, returns the number
        // of microtasks that ran in this round.
        int RunMicrotasks(Isolate* isolate);

        // Iterate all pending Microtasks in this queue as strong roots, so that
        // builtins can update the queue directly without the write barrier.
        void IterateMicrotasks(RootVisitor* visitor);

        // Microtasks scope depth represents nested scopes controlling microtasks
        // invocation, which happens when depth reaches zero.
        void IncrementMicrotasksScopeDepth() { ++microtasks_depth_; }
        void DecrementMicrotasksScopeDepth() { --microtasks_depth_; }
        int GetMicrotasksScopeDepth() const /*override*/;

        // Possibly nested microtasks suppression scopes prevent microtasks
        // from running.
        void IncrementMicrotasksSuppressions() { ++microtasks_suppressions_; }
        void DecrementMicrotasksSuppressions() { --microtasks_suppressions_; }
        bool HasMicrotasksSuppressions() const
        {
            return microtasks_suppressions_ != 0;
        }

#ifdef DEBUG
        // In debug we check that calls not intended to invoke microtasks are
        // still correctly wrapped with microtask scopes.
        void IncrementDebugMicrotasksScopeDepth() { ++debug_microtasks_depth_; }
        void DecrementDebugMicrotasksScopeDepth() { --debug_microtasks_depth_; }
        bool DebugMicrotasksScopeDepthIsZero() const
        {
            return debug_microtasks_depth_ == 0;
        }
#endif

        void set_microtasks_policy(v8::MicrotasksPolicy microtasks_policy)
        {
            microtasks_policy_ = microtasks_policy;
        }
        v8::MicrotasksPolicy microtasks_policy() const { return microtasks_policy_; }

        void FireMicrotasksCompletedCallback(Isolate* isolate) const;

        intptr_t capacity() const { return capacity_; }
        intptr_t size() const { return size_; }
        intptr_t start() const { return start_; }

        Microtask get(intptr_t index) const;

        MicrotaskQueue* next() const { return next_; }
        MicrotaskQueue* prev() const { return prev_; }

        static const size_t kRingBufferOffset;
        static const size_t kCapacityOffset;
        static const size_t kSizeOffset;
        static const size_t kStartOffset;
        static const size_t kFinishedMicrotaskCountOffset;

        static const intptr_t kMinimumCapacity;

    private:
        void OnCompleted(Isolate* isolate);

        MicrotaskQueue();
        void ResizeBuffer(intptr_t new_capacity);

        // A ring buffer to hold Microtask instances.
        // ring_buffer_[(start_ + i) % capacity_] contains |i|th Microtask for each
        // |i| in [0, size_).
        intptr_t size_ = 0;
        intptr_t capacity_ = 0;
        intptr_t start_ = 0;
        Address* ring_buffer_ = nullptr;

        // The number of finished microtask.
        intptr_t finished_microtask_count_ = 0;

        // MicrotaskQueue instances form a doubly linked list loop, so that all
        // instances are reachable through |next_|.
        MicrotaskQueue* next_ = nullptr;
        MicrotaskQueue* prev_ = nullptr;

        int microtasks_depth_ = 0;
        int microtasks_suppressions_ = 0;
#ifdef DEBUG
        int debug_microtasks_depth_ = 0;
#endif

        v8::MicrotasksPolicy microtasks_policy_ = v8::MicrotasksPolicy::kAuto;

        bool is_running_microtasks_ = false;
        using CallbackWithData = std::pair<MicrotasksCompletedCallbackWithData, void*>;
        std::vector<CallbackWithData> microtasks_completed_callbacks_;
    };

} // namespace internal
} // namespace v8

#endif // V8_MICROTASK_QUEUE_H_
